﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Floor.cs" company="">
//   
// </copyright>
// <summary>
//   The floor.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

 // --------------------------------------------------------------------------------------------------------------------
// <copyright file="Floor.cs" company="">
// </copyright>
// <summary>
//   The floor.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

/* 
 * Author: Mikkel Thordal Andersen, Barbara Carboni, Juan Mompean Esteban
 * Created: 17.11.2011
 */

namespace ITU_vs_Zombies.Scenery
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;

    using ITU_vs_Zombies.Character;
    using ITU_vs_Zombies.Game;
    using ITU_vs_Zombies.Graphics;
    using ITU_vs_Zombies.Movement;
    using ITU_vs_Zombies.Stuff;

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;

    /// <summary>
    /// The floor.
    /// </summary>
    [Serializable]
    public class Floor
    {
        #region Constants and Fields

        /// <summary>
        /// The image.
        /// </summary>
        public readonly string Image;

        /// <summary>
        /// The position.
        /// </summary>
        public Vector2 position;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Floor"/> class.
        /// </summary>
        /// <param name="picture">
        /// The picture.
        /// </param>
        /// <param name="number">
        /// The number.
        /// </param>
        /// <param name="active">
        /// </param>
        /// <param name="maxNumberOfEnemies">
        /// The max Number Of Enemies.
        /// </param>
        public Floor(string picture, int number, bool active, uint maxNumberOfEnemies)
        {
            Contract.Requires(picture != null);
            this.ExtraObjects = new List<ExtraObject>();
            this.Enemies = new List<Enemy>();
            this.Players = new List<Player>();
            this.DisposeEnemies = new List<Enemy>();
            this.DisposeExtraObjects = new List<ExtraObject>();
            this.Image = picture;
            this.position = new Vector2(0, 0);
            this.Number = number;
            this.Active = active;
            this.MaxNumberOfEnemies = maxNumberOfEnemies;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets a value indicating whether Active.
        /// </summary>
        public bool Active { get; set; }

        /// <summary>
        /// Gets or sets Background.
        /// </summary>
        public Background Background { get; set; }

        /// <summary>
        /// Gets the enemies on th floor
        /// </summary>
        public List<Enemy> Enemies { get; private set; }

        /// <summary>
        /// Gets the extraobjects on the floor
        /// </summary>
        public List<ExtraObject> ExtraObjects { get; private set; }

        /// <summary>
        /// Gets Height.
        /// </summary>
        public int Height
        {
            get
            {
                Contract.Requires(Client.GraphicsDevice != null);
                return Client.GraphicsDevice.Viewport.Height;
            }
        }

        /// <summary>
        /// Gets or sets MaxNumberOfEnemies.
        /// </summary>
        public uint MaxNumberOfEnemies { get; set; }

        /// <summary>
        /// Gets Number.
        /// </summary>
        public int Number { get; private set; }

        /// <summary>
        /// Gets or sets NumberOfSpawnedEnemies.
        /// </summary>
        public uint NumberOfSpawnedEnemies { get; set; }

        /// <summary>
        /// Gets the player
        /// </summary>
        public List<Player> Players { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether Visited.
        /// </summary>
        public bool Visited { get; set; }

        /// <summary>
        /// Gets Width.
        /// </summary>
        public float Width
        {
            get
            {
                Contract.Requires(this.Background != null);
                return this.Background.Width;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets DisposeEnemies.
        /// </summary>
        private List<Enemy> DisposeEnemies { get; set; }

        /// <summary>
        /// Gets or sets DisposeExtraObjects.
        /// </summary>
        private List<ExtraObject> DisposeExtraObjects { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// The collision.
        /// </summary>
        /// <param name="position1">
        /// The position 1.
        /// </param>
        /// <param name="position2">
        /// The position 2.
        /// </param>
        /// <param name="animation1">
        /// The animation 1.
        /// </param>
        /// <param name="animation2">
        /// The animation 2.
        /// </param>
        /// <returns>
        /// The collision.
        /// </returns>
        /// Author: Juan Mompean Esteban
        [Pure]
        public static bool Collision(Vector2 position1, Vector2 position2, Animation animation1, Animation animation2)
        {
            Contract.Requires(animation1 != null);
            Contract.Requires(animation2 != null);
            bool resultX = position1.X <= position2.X && (position1.X + animation1.FrameWidth) > position2.X;

            resultX |= position1.X >= position2.X && position1.X < (position2.X + animation2.FrameWidth);

            bool resultY = position1.Y < position2.Y && (position1.Y + animation1.FrameHeight) > position2.Y;

            resultY |= position1.Y > position2.Y && position1.Y < (position2.Y + animation2.FrameHeight);

            return resultX && resultY;
        }

        /// <summary>
        /// The add enemy.
        /// </summary>
        /// <param name="enemy">
        /// The enemy.
        /// </param>
        public void AddEnemy(Enemy enemy)
        {
            Contract.Requires(enemy != null);
            Contract.Requires(Client.Content != null);
            Contract.Requires(Client.GraphicsDevice != null);
            Contract.Ensures(this.Enemies.Contains(enemy));
            this.Enemies.Add(enemy);
            enemy.LoadContent();
            enemy.Position = new Vector2(0, Client.GraphicsDevice.Viewport.Height - enemy.Animation.FrameHeight);
        }

        /// <summary>
        /// The add extra object.
        /// </summary>
        /// <param name="extraObject">
        /// The extra object.
        /// </param>
        public void AddExtraObject(ExtraObject extraObject)
        {
            Contract.Requires(extraObject != null);
            Contract.Requires(Client.Content != null);
            Contract.Requires(Client.GraphicsDevice != null);
            Contract.Ensures(this.ExtraObjects.Contains(extraObject));
            this.ExtraObjects.Add(extraObject);
            extraObject.LoadContent();
        }

        /// <summary>
        /// The add player.
        /// </summary>
        /// <param name="player">
        /// The player.
        /// </param>
        /// Author: Mikkel Thordal Andersen
        public void AddPlayer(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Client.Content != null);
            Contract.Requires(Client.GraphicsDevice != null);
            Contract.Ensures(this.Players.Contains(player));
            this.Players.Add(player);
            player.LoadContent();
            if (this.Active && this.Players.Count == 1 && !Client.IsLoaded)
            {
                player.MovementEvent += this.Move;

                var knife = new Knife("Stuff\\knife", 1, this.Number, new Vector2(), 20, 5, @"Sounds\Stab");
                Client.MainPlayer.Weapons.Add(knife);
            }
        }

        /// <summary>
        /// The dispose.
        /// </summary>
        public void Dispose()
        {
            this.Players.ForEach(player => player.Dispose());

            this.Enemies.ForEach(enemy => enemy.Dispose());

            this.ExtraObjects.ForEach(extraObject => extraObject.Dispose());

            this.DisposeEnemies.ForEach(disposeEnemy => disposeEnemy.Dispose());

            this.DisposeExtraObjects.ForEach(disposeExtraObject => disposeExtraObject.Dispose());

            // this.texture.Dispose();
            this.Players.Clear();
            this.Enemies.Clear();
            this.ExtraObjects.Clear();
            this.DisposeEnemies.Clear();
            this.DisposeExtraObjects.Clear();
        }

        /// <summary>
        /// The draw.
        /// </summary>
        /// <param name="spriteBatch">
        /// The sprite batch.
        /// </param>
        public void Draw(SpriteBatch spriteBatch)
        {
            Contract.Requires(spriteBatch != null);
            this.Background.Draw(spriteBatch);

            this.Players.ForEach(player => player.Draw(spriteBatch));

            this.Enemies.ForEach(enemy => enemy.Draw(spriteBatch));

            this.ExtraObjects.ForEach(extraObject => extraObject.Draw(spriteBatch));
        }

        /// <summary>
        /// The enemies near.
        /// </summary>
        /// <param name="player">
        /// The player.
        /// </param>
        /// <param name="direction">
        /// The direction of the player.
        /// </param>
        /// <param name="distance">
        /// The distance of the weapon.
        /// </param>
        /// <returns>
        /// A list of the enemies in the specified distance.
        /// </returns>
        public List<Enemy> EnemiesNear(Player player, MovementsEnum direction, uint distance)
        {
            Contract.Requires(this.Enemies != null);
            return this.Enemies.Where(enemy => IsTheEnemyNear(direction, player, enemy, distance)).ToList();
        }

        /// <summary>
        /// The initialize.
        /// </summary>
        public void Initialize()
        {
            this.Background = new Background(this.Image);
        }

        /// <summary>
        /// The load content.
        /// </summary>
        public void LoadContent()
        {
            Contract.Requires(Client.Content != null);
            Contract.Requires(Client.GraphicsDevice != null);
            Contract.Requires(this.Background != null);

            this.Background.LoadContent();

            foreach (Player player in this.Players)
            {
                player.LoadContent();
            }

            foreach (Enemy enemy in this.Enemies)
            {
                enemy.LoadContent();
            }

            foreach (ExtraObject extraObject in this.ExtraObjects)
            {
                extraObject.LoadContent();
            }
        }

        /// <summary>
        /// The move.
        /// </summary>
        /// <param name="playerMovement">
        /// The player movement.
        /// </param>
        /// Author: Mikkel Thordal Andersen
        public void Move(MovementsEnum playerMovement)
        {
            Contract.Requires(this.Players.Count > 0);
            switch (playerMovement)
            {

                case MovementsEnum.Left:
                    if (!Client.MainPlayer.IsGraphicallyMoving)
                    {
                        
                        this.position.X += Player.Speed;
                        this.Background.Move(MovementsEnum.Left, Player.Speed);
                    }

                    break;

                case MovementsEnum.Right:
                    if (!Client.MainPlayer.IsGraphicallyMoving)
                    {
                        this.position.X -= Player.Speed;
                        this.Background.Move(MovementsEnum.Right, Player.Speed);
                    }

                    break;
            }

            this.position.X = MathHelper.Clamp(
                this.position.X, -(this.Background.Width - Client.GraphicsDevice.Viewport.Width), 0);
        }

        /// <summary>
        /// The nearest player.
        /// </summary>
        /// <param name="position">
        /// The position.
        /// </param>
        /// <returns>
        /// The nearest player to that position.
        /// </returns>
        /// Author: Barbara Carboni, Juan Mompean Esteban
        public Player NearestPlayer(Vector2 position)
        {
            Player nearestPlayer = null;
            foreach (Player player in this.Players)
            {
                if (nearestPlayer == null
                    ||
                    Math.Abs(nearestPlayer.RealPosition.X - position.X) > Math.Abs(player.RealPosition.X - position.X))
                {
                    nearestPlayer = player;
                }
            }

            return nearestPlayer;
        }

        /// <summary>
        /// The nearest weapon.
        /// </summary>
        /// <param name="position">
        /// The position.
        /// </param>
        /// <returns>
        /// The nearest weapon to that position.
        /// </returns>
        /// Author: Barbara Carboni, Juan Mompean Esteban
        public Weapon NearestWeapon(Vector2 position)
        {
            Weapon nearestWeapon = null;
            foreach (Weapon weapon in this.ExtraObjects.Where(eo => eo is Weapon))
            {
                if (nearestWeapon == null
                    || Math.Abs(nearestWeapon.Position.X - position.X) > Math.Abs(weapon.Position.X - position.X))
                {
                    nearestWeapon = weapon;
                }
            }

            return nearestWeapon;
        }

        /// <summary>
        /// The update.
        /// </summary>
        /// <param name="gameTime">
        /// The game time.
        /// </param>
        public void Update(GameTime gameTime)
        {
            Contract.Requires(gameTime != null);
            Contract.Requires(this.Background != null);
            foreach (Player player in this.Players)
            {
                player.Update(gameTime, this.position);
            }

            foreach (Enemy enemy in this.Enemies)
            {
                enemy.Update(gameTime, this.position);
            }

            foreach (ExtraObject extraobject in this.ExtraObjects)
            {
                extraobject.Update(gameTime, this.position);
            }

            this.Background.Update(gameTime, this.position);

            this.RemoveDeadEnemies();
            this.PickUpExtraObjects();
            this.CollisionProjectiles();
        }

        #endregion

        #region Methods

        /// <summary>
        /// The is the enemy near.
        /// </summary>
        /// <param name="direction">
        /// The direction.
        /// </param>
        /// <param name="player">
        /// The player.
        /// </param>
        /// <param name="enemy">
        /// The enemy.
        /// </param>
        /// <param name="distance">
        /// The distance.
        /// </param>
        /// <returns>
        /// The is the zombie near.
        /// </returns>
        /// Author: Barbara Carboni, Juan Mompean Esteban
        private static bool IsTheEnemyNear(MovementsEnum direction, Player player, Enemy enemy, uint distance)
        {
            Contract.Requires(direction == MovementsEnum.Right || direction == MovementsEnum.Left);
            bool result;
            if (direction == MovementsEnum.Right)
            {
                result = player.RealPosition.X <= enemy.Position.X
                         && (player.RealPosition.X + player.Animation.FrameWidth + distance) >= enemy.Position.X;
            }
            else
            {
                result = player.RealPosition.X >= enemy.Position.X
                         && player.RealPosition.X <= (enemy.Position.X + enemy.Animation.FrameWidth + distance);
            }

            return result
                   &&
                   ((player.RealPosition.Y >= enemy.Position.Y
                     && (player.RealPosition.Y + player.Animation.FrameHeight) >= enemy.Position.Y)
                    ||
                    (player.RealPosition.Y <= enemy.Position.Y
                     && player.RealPosition.Y <= (enemy.Position.Y + enemy.Animation.FrameHeight)));
        }

        /// <summary>
        /// The collision of the projectiles.
        /// </summary>
        /// Author: Barbara Carboni, Juan Mompean Esteban
        private void CollisionProjectiles()
        {
            foreach (ExtraObject extraObject in this.ExtraObjects.Where(extraObject => extraObject is Projectile))
            {
                var projectile = (Projectile)extraObject;
                Enemy enemyCollision =
                    this.Enemies.Find(
                        enemy => Collision(projectile.Position, enemy.Position, projectile.Animation, enemy.Animation));
                if (enemyCollision != null)
                {
                    enemyCollision.Life -= projectile.Damage <= enemyCollision.Life
                                               ? projectile.Damage
                                               : enemyCollision.Life;
                }
            }

            foreach (Enemy enemy in this.Enemies)
            {
                this.DisposeExtraObjects.AddRange(
                    this.ExtraObjects.Where(
                        extraObject =>
                        Collision(extraObject.Position, enemy.Position, extraObject.Animation, enemy.Animation) && extraObject is Projectile));
                this.ExtraObjects.RemoveAll(
                    extraObject =>
                    Collision(extraObject.Position, enemy.Position, extraObject.Animation, enemy.Animation) && extraObject is Projectile);
            }
        }

        /// <summary>
        /// The pick up extra objects.
        /// </summary>
        /// 
        /// Author: Juan Mompean Esteban
        private void PickUpExtraObjects()
        {
            foreach (Player player in this.Players)
            {
                foreach (ExtraObject extraObject in
                    this.ExtraObjects.Where(
                        extraObject =>
                        Collision(player.RealPosition, extraObject.Position, player.Animation, extraObject.Animation)
                        && !(extraObject is Projectile)))
                {
                    if (extraObject is Life)
                    {
                        player.Life += (((Life)extraObject).Value + Client.MainPlayer.Life)
                                       <= Client.MainPlayer.MaximumLife
                                           ? ((Life)extraObject).Value
                                           : (Client.MainPlayer.MaximumLife - Client.MainPlayer.Life);
                        extraObject.Sound.Play();
                        this.DisposeExtraObjects.Add(extraObject);
                    }
                    else if (extraObject is Money)
                    {
                        extraObject.Sound.Play();
                        player.Money += ((Money)extraObject).Value;
                        this.DisposeExtraObjects.Add(extraObject);
                    }
                    else if (extraObject is Weapon)
                    {
                        player.Weapons.Add((Weapon)extraObject);
                    }
                }

                this.ExtraObjects.RemoveAll(
                    extraObject =>
                    Collision(player.RealPosition, extraObject.Position, player.Animation, extraObject.Animation)
                    && !(extraObject is Projectile));
            }
        }

        /// <summary>
        /// The remove dead enemies.
        /// </summary>
        private void RemoveDeadEnemies()
        {
            foreach (Enemy enemy in this.Enemies.Where(e => e.Life <= 0))
            {
                Client.MainPlayer.Score += enemy.MaximumLife;
            }

            this.DisposeEnemies.AddRange(this.Enemies.Where(enemy => enemy.Life <= 0));

            this.Enemies.RemoveAll(enemy => enemy.Life <= 0);
        }

        #endregion
    }
}